﻿using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

namespace AMEE.BeinControl
{
    /// <summary>
    /// Event argument containing a string message received from the Network (a MCU).
    /// </summary>
    class MessageReceivedArgument : EventArgs
    {
        /// <summary>Gets or sets the message.</summary>
        /// <value>The message.</value>
        public String Message { get; set; }
        /// <summary>
        /// Initializes a new instance of the <see cref="MessageReceivedArgument"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        public MessageReceivedArgument(string message) : base() {
            this.Message = message;
        }
    }




    /// <summary>
    /// 
    /// </summary>
    class Listener : IDisposable
    {
        /// <summary>
        /// Event Occurs when [message received].
        /// </summary>
        public event EventHandler<MessageReceivedArgument> MessageReceived;

        #region Fields

        bool _disposed = false;
        Thread _receiver;
        TcpClient _client;
        bool _thrRunning;
        object _thrRunLock = new object(); 

        #endregion


        #region Properties

        /// <summary>
        /// Gets the ID (IP-Subaddress+port).
        /// </summary>
        /// <value>
        /// The ID.
        /// </value>
        public String ID { get; private set; }

        #endregion


        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="Listener"/> class.
        /// </summary>
        /// <param name="ip">The ip.</param>
        /// <param name="port">The port.</param>
        /// <exception cref="System.Net.Sockets.SocketException">An error occurred when accessing the socket.</exception>
        /// <exception cref="AMEE.BeinControl.BeinControlException"> Invalid port number. or Invalid IP adress format. </exception>
        public Listener(string ip, int port) {
            checkCtorParams(ip, port); // throws Exc
            _client = new TcpClient(ip, port);
            this.ID = ip.Substring(ip.LastIndexOf('.')) + "/" + port;
            _thrRunning = true;
            _receiver = new Thread(cl => this.MCUReader(cl));
            _receiver.Name = "Recv Thrd " + this.ID;
        }

        ~Listener() {
            Dispose(false);
        }

        #endregion


        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion


        #region Public

        /// <summary>
        /// Starts listening.
        /// </summary>
        /// <returns>true, if receiver initialization succeeded or already is running. False otherwise</returns>
        public bool Start() {
            try {
                if (_receiver.IsAlive) {
                    return true;
                }
                lock (_thrRunLock) {
                    _thrRunning = true;
                }
                _receiver.Start(_client);
            }
            catch (Exception ex) {
                Debug.WriteLine("Listener.Start", "Error");
                Debug.WriteLine(ex.Message);
                return false;
            }
            return true;
        }

        public void Stop() {
            if (_receiver.IsAlive) {
                lock (_thrRunLock) {
                    _thrRunning = false;
                }
            }
        } 

        #endregion


        #region Helper / Private

        /// <summary>
        /// Reads Strings from MCU and raises as Event.
        /// </summary>
        /// <param name="temp">TCPClient</param>
        void MCUReader(object temp) {
            TcpClient net = (TcpClient)temp;

            try {
                using (NetworkStream netStream = net.GetStream()) {
                    //StreamReader str = new StreamReader(netStream);
                    //str.re
                    if (!netStream.CanRead) {
                        Debug.WriteLine(Thread.CurrentThread.Name + 
                            " Listener.MCUReader - NetworkStream dosn't support reading.", "Warning");
                        return;
                    }
                    byte[] bytes = new byte[net.ReceiveBufferSize];
                    StringBuilder myCompleteMessage = new StringBuilder();
                    //Console.WriteLine("Reader thread startet");
                    while (_thrRunning) {
                        try {
                            int numberOfBytesRead = 0;
                            do {
                                numberOfBytesRead = netStream.Read(bytes, 0, bytes.Length);
                                myCompleteMessage.AppendFormat("{0}", Encoding.ASCII.GetString(bytes, 0, numberOfBytesRead));
                            }
                            while (netStream.DataAvailable);

                            this.RaiseReceivedEvent(myCompleteMessage.ToString());
                            myCompleteMessage.Clear();
                        }


                        catch (Exception) {
                            lock (_thrRunLock) {
                                _thrRunning = false;
                            }
                            Debug.WriteLine("Listener.MCUReader: Read error", "Error");
                            netStream.Dispose();
                        }
                    }
                }
            }
            catch (ThreadAbortException) {
                Debug.WriteLine("Listener.MCUReader Thread abort exception.", "Error");
            }
            catch (Exception ex) {
                Debug.WriteLine("Listener.MCUReader - NetworkStream GetStream failed!", "Error");
                Debug.WriteLine(ex.Message);
            }
        }


        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protected virtual void Dispose(bool disposing) {
            // Check to see if Dispose has already been called.
            if (!this._disposed) {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing) {
                    // Dispose managed resources.
                    lock (_thrRunLock) {
                        _thrRunning = false;
                    }
                    if (_receiver != null) {
                        if (_receiver.IsAlive) {
                            _receiver.Join(5000);
                        }
                    }
                    if (_client != null)
                        _client.Close();
                }

                // Note disposing has been done.
                _disposed = true;

            }
        }

        /// <summary>
        /// Checks the ctor params.
        /// </summary>
        /// <param name="ip">The ip.</param>
        /// <param name="port">The port.</param>
        /// <exception cref="AMEE.BeinControl.BeinControlException">
        /// Invalid port number.
        /// or
        /// Invalid IP adress format.
        /// </exception>
        void checkCtorParams(string ip, int port) {
            String regex = "(\\d{1,3}\\.){3}\\d{1,3}";
            Match match = Regex.Match(ip, regex);
            if (match.Success) {
                if (port < 1)
                    throw new BeinControlException("Invalid port number.", new ArgumentException("invalid port number", "port"));
            }
            else
                throw new BeinControlException("Invalid IP adress format.", new ArgumentException("invalid (IPv4) format", "ip"));
        }


        /// <summary>
        /// Raises the received event.
        /// </summary>
        /// <param name="message">The message.</param>
        protected virtual void RaiseReceivedEvent(String message) {
            Debug.WriteLine(Thread.CurrentThread.Name + " mesg received: " + message);
            // Make a temporary copy of the event to avoid possibility of 
            // a race condition if the last subscriber unsubscribes 
            // immediately after the null check and before the event is raised.
            EventHandler<MessageReceivedArgument> handlerCopy = MessageReceived;

            MessageReceivedArgument evArg = new MessageReceivedArgument(message);
            // Event will be null if there are no subscribers 
            if (handlerCopy != null) {
                evArg.Message += String.Format(" at {0}", DateTime.Now.ToString());
                handlerCopy(this, evArg);
            }
        }

        #endregion

    }
}
